背景
之前得到了一台性能强劲的 EPYC 9654 服务器, 在上面非常愉快的跑了许多数据分析任务, 甚至是 LLM. 但最近想用它处理一些私密的数据的时候, 就遇到了困难…
我并不是这台服务器唯一的用户, 这台公交车服务器的权限管理混乱的十分典型 - 所有人的账号都共用一个密码, 所有人都在 sudo 组, 服务器的 root 密码在各个微信/QQ群被转发来转发去, 同时服务器的 SSH 还被内网穿透转发到了公网. 甚至连这台服务器的 IPMI 都和操作系统共用了同一个并不算强的用户名和密码对.
这样的环境可以说是和”安全”没有任何一丝关系, 任何一个用户或外来攻击者都可以轻易读取到我硬盘上或内存中的数据. 但这样的服务器在学校内网又大规模存在, 毕竟大家都只想追求便捷, 没人在意安全. 如果我加固服务器的安全措施, 会给其他用户带来不便.
于是乎, 我开始探索如何要在如此混沌的环境下安全的计算我的数据, 经过简单的搜索, 发现了 AMD Secure Encrypted Virtualization (SEV) 这一技术.
嗨呀, 这不可信计算嘛. 各大云厂商包括 Azure, AWS, GCP 都对此有支持, 看起来算是基本成熟的技术, 那在自己的服务器上配置应该也并非难事.(flag
尽管可信计算在民用领域口碑不佳, 常被用于搞 vendor lock-in 或是 DRM, 做各类限制用户自由和选择权的反用户功能, 通过垄断安全和可信的定义权以无底线的谋求商业利益. (说的就是你, Google 和 Play Integrity)
但至少在主流桌面端 x86 架构这边, Secure Boot 还可以自由 opt-out, 加密虚拟化还是服务器 CPU 的特权. 除了用户需要对 SEV 这种厂商专有功能有依赖和信任外, 整体可以算是一个良好的安全功能. 让我们来尝试一下它.
AMD 的 SEV 有以下几种:
- SEV, 安全加密虚拟化. 加密虚拟机内存.
- SEV-ES, 安全加密虚拟化-加密状态. 加密虚拟机内存以及使用的寄存器.
- SEV-SNP, 安全加密虚拟化-安全嵌套分页. 在前两者基础上加密内存映射, 保证内存完整性.
SEV 和 SEV-ES 需要二代 EPYC 或以上, SEV-SNP 需要三代 EPYC 或以上. 作为 EPYC 9004 系列, 我们直接选择尝试最新的 SEV-SNP.
配环境
整个互联网上关于 SEV-SNP 的资料并不多, 只找到一份详细的 SEV-ES 的资料 (我尝试使用了这篇文章里和 QEMU WIki 的启动参数还启动失败了), 故我记录下 SEV-SNP 配置过程, 以供参考.
Host Bios
首先, 需要修改主机 BIOS. 我这台服务器的主板是 H13SSL-N, SEV 相关配置默认没有开启. 直接重启服务器, 连接到 IPMI, 通过网页操作 BIOS 修改如下配置:
开启 SMEE
(内存加密), 开启 SEV Control
, 将 SEV-ES ASID Space Limit
调整为 > 1 的值以开启 SEV-ES, 开启 RMP Table
.
BIOS CPU 设置页
开启 SEV-SNP Support
BIOS 南桥设置页
Host Linux 配置
CPU 和 BIOS 提供最基本的支持后, 还需要 Host 的 Hypervisor, QEMU 以及 QEMU 运行的 OVMF 固件支持.
目前 SEV-SNP 作为一种新技术, 仅仅在几天前才在最新的 Ubuntu 25.04 得到作为 Host 侧的支持. 我使用的 Ubuntu 22.04 LTS 显然是无缘这类功能更新了, 只能自己编译需要的工具.
还好 GitHub 上有好心人已经整理好了相关的脚本, 我们可以直接拉取使用. (理论上你应该在一个可信的环境下构建所需的工具, 特别是下面的 OVMF 固件, 不过我这里暂时偷懒了)
Ubuntu 22.04 LTS 在构建时遇到了一些依赖缺失, 比如 nasm
iasl
debhelper
之类的, 根据报错确认问题, 手动补安装即可.1
2
3
4
5
6
7
8
9git clone https://github.com/AMDESE/AMDSEV.git
git checkout snp-latest
# 构建 QEMU
./build.sh qemu
# 构建 QEMU 使用的 OVMF (UEFI 固件)
./build.sh ovmf
# 构建支持 SEV-SNP 的 kernel
./build.sh kernel
sudo cp kvm.conf /etc/modprobe.d/
构建成功之后使用 apt 安装构建出的 Host kernel, 重启后在 GRUB 选择新 kernel 启动.1
2
3cd linux
apt install ./linux-headers-6.11.0-rc3-snp-host-85ef1ac03941_6.11.0-rc3-g85ef1ac03941-2_amd64.deb ./linux-libc-dev_6.11.0-rc3-g85ef1ac03941-2_amd64.deb ./linux-image-6.11.0-rc3-snp-host-85ef1ac03941_6.11.0-rc3-g85ef1ac03941-2_amd64.deb
reboot
等待 Host 重启之后, 已经可以在 dmesg 中看到 SEV-SNP 正常加载的日志.1
2
3
4
5
6
7
8
9
10
11
12
13test@epyc:~$ uname -a
Linux epyc 6.11.0-rc3-snp-host-85ef1ac03941 #2 SMP Sun Aug 24 02:32:26 CST 2025 x86_64 x86_64 x86_64 GNU/Linux
test@epyc:~$ sudo dmesg | grep SEV
[ 0.000000] SEV-SNP: RMP table physical range [0x0000000015500000 - 0x0000000075afffff]
[ 0.003519] SEV-SNP: Reserving start/end of RMP table on a 2MB boundary [0x0000000015400000]
[ 0.003526] SEV-SNP: Reserving start/end of RMP table on a 2MB boundary [0x0000000075a00000]
[ 13.101084] ccp 0000:03:00.5: SEV API:1.55 build:36
[ 13.101094] ccp 0000:03:00.5: SEV-SNP API:1.55 build:36
[ 13.127843] kvm_amd: SEV enabled (ASIDs 128 - 1006)
[ 13.127844] kvm_amd: SEV-ES enabled (ASIDs 1 - 127)
[ 13.127845] kvm_amd: SEV-SNP enabled (ASIDs 1 - 127)
test@epyc:~$ cat /sys/module/kvm_amd/parameters/sev_snp
Y
启动 Guest
准备好一个安装好 Linux 的 Guest 磁盘, 以 qcow2 格式存储并传输到 Host 上, 我这里选择 Debian 13. 相对新的发行版已经内置了 SEV-SNP 的 Guest 支持.1
2
3# 启动虚拟机
# P.S. 我编辑了下脚本, 加入 -netdev user,id=vmnic,hostfwd=tcp::8000-:22 -device e1000,netdev=vmnic,romfile= -vnc :1 -device virtio-vga 参数, 开启端口转发和 VNC 便于观察虚拟机状态
sudo ./launch-qemu.sh -hda ../debian13-secure.qcow2 -sev-snp -mem 16384 -smp 16
通过 SSH 或其他方法连接到运行的虚拟机, 通过 dmesg 可以发现客户端的 SEV-SNP 已经启动.1
2
3
4
5
6
7
8root@sevsnp:~# dmesg | grep SEV
[ 2.350837] Memory Encryption Features active: AMD SEV SEV-ES SEV-SNP
[ 2.350855] SEV: Status: SEV SEV-ES SEV-SNP
[ 2.470226] SEV: APIC: wakeup_secondary_cpu() replaced with wakeup_cpu_via_vmgexit()
[ 3.806622] SEV: Using SNP CPUID table, 29 entries present.
[ 3.806628] SEV: SNP running at VMPL0.
[ 4.118602] SEV: SNP guest platform device initialized.
[ 15.268999] sev-guest sev-guest: Initialized SEV guest driver (using VMPCK0 communication key)
成功启动受 SEV-SNP 保护的 Guest 虚拟机
对 Guest 进行 Attestation
看起来我们已经运行了受 SEV-SNP 保护的虚拟机, 这个 VM 的内存和寄存器状态受到 AMD Secure Processor 的保护, 无法被不受信任的 Hypervisor (在这里就是我们的 EPYC 9654 服务器) 读取或篡改.
但设想另一种情况: 假设有个足够高明的攻击者, 在我将上面的 debian13 qcow2 镜像传输到 EPYC 9654 上时, 立即对其 kernel 进行了篡改和替换, 使其明明没有受到 SEV-SNP 保护的情况下输出了 SEV-SNP 相关的日志, 也能得到上面的结果, 让我以为它受到保护了.
换句话说, 我们目前还无法证明这台虚拟机受到了保护. 要进一步确定虚拟机受到 SEV-SNP 保护, 且运行的是受信任的软件, 我们需要对其进行远程 Attestation (度量).
准备 measurement
让我们转移阵地到一个受信任的设备上, 比如我的 HomeLab, 进行一些准备工作.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35# 安装生成度量值需要的工具
pip install sev-snp-measure
# 重新在可信环境下构建需要的 OVMF
git clone https://github.com/AMDESE/AMDSEV.git
cd AMDSEV
git checkout snp-latest
# 这里构建前要做个小修改, 把 common.sh 中的 OvmfPkg/OvmfPkgX64.dsc 替换为 OvmfPkg/AmdSev/AmdSevX64.dsc
# 见 https://github.com/virtee/sev-snp-measure/issues/26#issuecomment-1636995518
touch ovmf/OvmfPkg/AmdSev/Grub/grub.efi # 这条 touch 来自 https://github.com/AMDESE/AMDSEV/issues/124#issuecomment-1336387966
./build.sh ovmf
cd ..
cp AMDSEV/ovmf/Build/AmdSev/DEBUG_GCC5/FV/OVMF.fd .
# 图方便, 直接从当前系统 (同样是 Ubuntu 22.04) 偷一个 kernel 和 initramfs 出来, Ubuntu 22.04 已经有作为 guest 时现成的 SNP 支持了
sudo cp /boot/vmlinuz .
# 得先把 sev-guest 这个需要的驱动打包进 initramfs 里
echo -e '\nsev-guest' > /etc/initramfs-tools/modules
sudo update-initramfs -u
sudo cp /boot/initrd.img .
# 再额外打包一个需要的 snpguest 工具进去
sudo unmkinitramfs -v initrd.img .
wget https://github.com/virtee/snpguest/releases/download/v0.9.1/snpguest
sudo chown root:root snpguest
sudo chmod 755 snpguest
sudo cp snpguest main/usr/bin
sudo cp /usr/bin/base64 main/usr/bin
cd main
find . | sudo cpio -o -H newc | gzip > ../myinitrd.cpio.gz
cd ..
# 生成需要的 measurement, 这是我们等下要验证的值
sev-snp-measure --mode snp --vcpus 16 --vcpu-type EPYC-v4 --ovmf OVMF.fd --kernel vmlinuz --initrd myinitrd.cpio.gz --append "console=ttyS0"
# Output: 4936d42a8e2c2e91f1f1160063097b43b5c0c3339f5f85586128356e16d4cf36ebe5657fbbf9c3163dcf606bdf7e542c
启动 Guest
我们把刚刚得到的 OVMF.fd
, vmlinuz
, myinitrd.cpio.gz
这三个文件复制到 EPYC 9654 上.1
2
3# 需要先编辑 launch-qemu.sh
# 在 -object sev-snp-guest,...,reduced-phys-bits=1 这行结尾追加 ,kernel-hashes=on
sudo ./launch-qemu.sh -sev-snp -bios .. -cpu EPYC-v4 -smp 16 -mem 16384 -kernel ../vmlinuz -initrd ../myinitrd.cpio.gz -append "console=ttyS0" -hda ""
从日志可以看到, 最终执行的真正指令是1
/mnt/ssd/qemu/AMDSEV/usr/local/bin/qemu-system-x86_64 -enable-kvm -cpu EPYC-v4 -machine q35 -netdev user,id=vmnic,hostfwd=tcp::8000-:22 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile= -vnc :1 -device virtio-vga -smp 16,maxcpus=255 -m 16384M,slots=5,maxmem=24576M -no-reboot -bios /mnt/ssd/qemu/OVMF.fd -machine confidential-guest-support=sev0,vmport=off -object memory-backend-memfd,id=ram1,size=16384M,share=true,prealloc=false -machine memory-backend=ram1 -object sev-snp-guest,id=sev0,policy=0x30000,cbitpos=51,reduced-phys-bits=1,kernel-hashes=on -kernel ../vmlinuz -append "console=ttyS0" -initrd ../myinitrd.cpio.gz -nographic -monitor pty -monitor unix:monitor,server,nowait
启动成功后, 由于我们目前没有挂载任何磁盘, initramfs 无法继续启动, 停留在了 busybox shell 中.
启动到了 initramfs 的虚拟机
进行 Attestation 并验证结果
回到我们受信任的机器, 生成一个现代安全领域非常常见的 nonce 用于防止重放攻击.1
2openssl rand -hex 64 > nonce.hex
# 此处为 3502ae46269024fda5eea969d942f15b9cf9708602709d97b0de1ceaa0b712c7a6ea5170310fd6f10e9f1ad223cb4ffbc8fd036a70846cb7d50f3086c64c2da0
再到我们的虚拟机, 把 nonce.hex 写入文件, 并生成 Attestation 报告:1
2
3
4
5
6echo '3502ae46269024fda5eea969d942f15b9cf9708602709d97b0de1ceaa0b712c7a6ea5170310fd6f10e9f1ad223cb4ffbc8fd036a70846cb7d50f3086c64c2da0' > nonce.hex
# 调用我们之前准备的 snpguest 工具生成 report.bin
snpguest report report.bin nonce.hex
# 把这个文件读取回来
cat report.bin | base64
# 输出为 AgAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAJ...<后略>
再把这个 report.bin 写回受信任的 HomeLab 机器, 调用 snpguest 进行验证:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# 下载 AMD 提供的 genoa (EPYC 9004) 的 CA 证书 (ark.pem + ask.pem)
snpguest fetch ca pem . genoa
# 根据 Attestation report 从 AMD KDS 服务下载 vcek.pem
# VCEK (Versioned Chip Endorsement Key) 对于每个 CPU 是不同的
snpguest fetch vcek -p genoa pem . report.bin
# 我们得到了 ARK (AMD Root Key) -> ASK (AMD SEK Key) -> VCEK 的信任链
snpguest verify certs .
# 输出:
# The AMD ARK was self-signed!
# The AMD ASK was signed by the AMD ARK!
# The VCEK was signed by the AMD ASK!
# 验证得到的 Attestation Report
snpguest verify attestation -p genoa . report.bin
# 输出:
# Reported TCB Boot Loader from certificate matches the attestation report.
# Reported TCB TEE from certificate matches the attestation report.
# Reported TCB SNP from certificate matches the attestation report.
# Reported TCB Microcode from certificate matches the attestation report.
# VEK signed the Attestation Report!
此时, 我们得到了一份确认由 AMD 签名的 Attestation Report, 可以使用 snpguest display report report.bin
阅读其内容.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135Attestation Report:
Version: 2
Guest SVN: 0
Guest Policy (0x30000):
ABI Major: 0
ABI Minor: 0
SMT Allowed: true
Migrate MA: false
Debug Allowed: false
Single Socket: false
Family ID:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Image ID:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
VMPL: 1
Signature Algorithm: 1
Current TCB:
TCB Version:
Microcode: 72
SNP: 21
TEE: 0
Boot Loader: 9
FMC: None
Platform Info (1):
SMT Enabled: true
TSME Enabled: false
ECC Enabled: false
RAPL Disabled: false
Ciphertext Hiding Enabled: false
Alias Check Complete: false
Key Information:
author key enabled: false
mask chip key: false
signing key: vcek
Report Data:
33 35 30 32 61 65 34 36 32 36 39 30 32 34 66 64
61 35 65 65 61 39 36 39 64 39 34 32 66 31 35 62
39 63 66 39 37 30 38 36 30 32 37 30 39 64 39 37
62 30 64 65 31 63 65 61 61 30 62 37 31 32 63 37
Measurement:
49 36 D4 2A 8E 2C 2E 91 F1 F1 16 00 63 09 7B 43
B5 C0 C3 33 9F 5F 85 58 61 28 35 6E 16 D4 CF 36
EB E5 65 7F BB F9 C3 16 3D CF 60 6B DF 7E 54 2C
Host Data:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ID Key Digest:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Author Key Digest:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Report ID:
C5 13 43 3F A5 F7 D2 F9 24 91 AE AE DD B2 33 D3
5A 23 4A 81 90 90 F4 CA A6 75 8B 38 A0 F3 05 8C
Report ID Migration Agent:
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
Reported TCB:
TCB Version:
Microcode: 72
SNP: 21
TEE: 0
Boot Loader: 9
FMC: None
CPUID Family ID: None
CPUID Model ID: None
CPUID Stepping: None
Chip ID:
59 54 5A 6F D7 AF 46 35 51 28 B9 55 4B 1A 0C 86
6D 88 1A A5 E6 5F CB 02 24 49 51 78 F1 0B 57 7D
C4 1D FC 64 82 57 E1 AC DB DB 2E 03 24 17 F2 02
A3 F1 D3 48 60 38 52 A3 10 62 7C 72 AA 27 68 BD
Committed TCB:
TCB Version:
Microcode: 72
SNP: 21
TEE: 0
Boot Loader: 9
FMC: None
Current Version: 1.55.36
Committed Version: 1.55.36
Launch TCB:
TCB Version:
Microcode: 72
SNP: 21
TEE: 0
Boot Loader: 9
FMC: None
Signature:
R:
23 48 89 73 E1 AE D6 B1 31 6C 8A 79 00 DF DA DB
84 CB C6 0A 1D EC AE 71 0D 7C E1 7A 33 C1 80 C1
86 1A A4 05 08 A0 A4 A9 C9 AD 5B 1B D8 03 A6 C2
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
S:
D8 AC 18 0F 3F 47 E8 F2 6A 8E AC 6C F2 A8 04 E3
1B 41 C0 4E F1 5A 51 30 79 8E C0 A0 69 61 73 EE
B2 FC 6B 6C 72 84 F3 1B 0E 3A 22 B1 9B AB 2B D1
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
可以看到其中包含我们生成的 Nonce (Report Data
区域), 且 Measurement
的值与我们在本地可信环境计算得到的一致.
至此, 我们只要信任 AMD, 我们就已经通过密码学手段确认: 这台虚拟机确实运行在一台真实的受信任的 AMD 系统上, 且其内存保密性和完整性受到 AMD SEV-SNP 技术保护, 且其正在运行我们在可信环境中取得的, 未经篡改的软件镜像 (包括 OVMF, Linux 内核, initramfs 以及启动时的 cmdline).
传入密钥并完成启动
经过 Attestation, 我们已经有理由相信我们在这台可疑的 EPYC 9654 服务器上成功启动了一个可信的虚拟机, 最困难的部分已经搞定了, 接下来只要简简单单启动一个完整的 Linux Distro 就可以开始计算了… 吗?
仔细想想会发现并非.
再回顾一下: 我们现在只是启动了指定的内核和 initramfs, 要启动一个完整的 Linux 环境进行计算任务, 我们还需要加载一个可信的 rootfs. 就算你头铁到不要 rootfs 直接把整个计算环境打包进 initramfs, 我们最终目的还是要把机密的数据传进这台 VM 的内存中进行计算.
直接将机密数据打包进 initramfs 是不可行的, 因为其本身是明文, 并未被加密. 同理, 直接打包任何形式的凭据, 密钥或私钥到 initramfs 都是不可行的.
通过网络或者终端将密钥或机密数据传输给 VM 同样不行, 因为这类通讯可以轻易被中间人 (Host) 的监听. 你可能马上会想到 SSH, 但 SSH 的安全性依赖 SSH Host Key 不被泄露, 可 Hypervisor 早已掌握存在于 initramfs 中的 SSH Host Key.
说了这么多, 其实根本的问题就是: Hypervisor 目前与我们加密的 VM 掌握的信息完全相同, 在 Hypervisor 成为天然中间人的情况下, 尽管我们知道存在这么一台加密的 VM, 我们却无法区分 VM 和 Hypervisor.
在曾经的 SEV-ES 中, 我们可以直接构建加密的 secret, 由 AMD CPU 解密后直接传递给 VM, 非常轻松的解决密钥传递的问题. 而新一代的 SEV-SNP 中, 这个功能看似被一个十分复杂的 SVSM 模块取代了. (相关讨论)
这个地方确实让我卡了一段时间, 看来还是现代密码学学的不够好. 其实巧妙利用我们上面完成的 Attestation 过程, 我们已经可以区分出加密的 VM 和 Hypervisor.
具体思路是:
- 可信环境中, 我们生成一个 Nonce
- 提供 Nonce 给加密的 VM, VM 使用 Nonce 作为 Report Data 生成 Attestation Report
- 加密 VM 在内存中生成一对公私钥
- 加密 VM 使用公钥作为 Report Data 生成第二份 Attestation Report
- 可信环境中, 我们验证这两份 Attestation Report. 第一份证明加密 VM 确实可信, 第二份则携带了经由 AMD 签名的公钥. 这个公钥无法被恶意的 Hypervisor 篡改
- 可信环境中使用公钥加密我们想要传递的 Secret (例如加密磁盘的密钥)
- 加密 VM 使用私钥在内存中解密讯息, 得到 Secret
需要注意的是, 这里的 initramfs 不能信任 tty 的输入, 不能直接提供 shell, 必须只能按上述流程接受必要的信息. 由于 Attestation Report 中包含了 initramfs 的 Hash, 只要合理编写 init 脚本, 我们是可以完全控制 initramfs 本身的行为的.
这个流程是相对完整的, 其实偷懒一点的话, 可以只生成并验证第二份 Attestation Report, 相当于使用了一个来自 VM 内部的 Nonce.
理论可行实践开始
再次回到可信的 HomeLab, 先准备一个加密过后的 rootfs:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34truncate -s 5G rootfs_enc.raw
sudo losetup -f rootfs_enc.raw
losetup -a # 沟槽的 snap 给我干到了 /dev/loop28, 以下就以这个为例
# 直接将整个虚拟磁盘用 LUKS 加密, 在此处设置密码, 以 "AMD,YES!" 为例
# 默认的 LUKS 没有数据完整性保证: https://security.stackexchange.com/questions/87367/does-luks-protect-the-filesystem-integrity
# 所以我们这里使用 LUKS2 并手动开启 integrity, 这目前还是个实验性选项
sudo cryptsetup luksFormat --type luks2 /dev/loop28 --integrity hmac-sha256
sudo cryptsetup luksOpen /dev/loop28 guest_rootfs
sudo mkfs.ext4 /dev/mapper/guest_rootfs
mkdir rootfs
sudo mount /dev/mapper/guest_rootfs rootfs
sudo debootstrap --arch=amd64 jammy rootfs http://mirror.nju.edu.cn/ubuntu/
sudo chroot rootfs # chroot 到 rootfs 进行设置
passwd root # 设置登录需要的密码, 你也可以创建新用户
# 配置下网络, 等下直接 SSH (终端密码登录是不安全的)
cat << EOF > /etc/netplan/01-config.yaml
network:
version: 2
ethernets:
enp0s1:
dhcp4: true
EOF
apt update && apt install ssh # 这里顺便记录下 SSH key, 如果只有 root 记得加公钥或者允许密码登录
exit # 退出 chroot
sudo umount rootfs
sudo cryptsetup luksClose guest_rootfs
sudo losetup -d /dev/loop28
再准备下实现了上述”握手”过程的 initramfs. 内置的 /init
脚本过于复杂, 我对其研究甚浅, 我选择直接整个替换掉, 反正该用照用.
以及, 这里如果你之前没有安装过 cryptsetup, 是刚刚搓 rootfs 时候装的, 之前解包的 initrd.img 里也可能没有 cryptsetup 相关内容. 需要重新解包一次. 你可能还需要在 /etc/initramfs-tools/modules
添加 dm_integrity
并重新更新且解包 initrd.img
.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# 此脚本整个替换掉原来的 initramfs 中的 init
# 我这里用了 age 这个工具来进行加密解密, 自己使用 CGO_ENABLED=0 编译之后就完全没有动态链接库了, 你可以打包自己熟悉的工具
# 目前实现的是手动交互版本, 如果你愿意配置网络的话, 可以做成无人值守的安全启动
LUKS_DEVICE="/dev/sda"
MAPPER_NAME="encroot"
FS_TYPE="ext4"
NEW_ROOT_MP="/new_root"
echo "==> [INITRAMFS] Starting LUKS init script..."
echo "==> [INITRAMFS] Mounting essential filesystems..."
mkdir /proc /sys
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
echo "==> [INITRAMFS] Loading crypto modules..."
modprobe sev-guest
modprobe dm_crypt
modprobe dm_integrity
echo "==> [INITRAMFS] Starting remote attestation process..."
read -r -p "Enter nonce:" NONCE
echo " -> Generating attestation report with nonce..."
echo -n $NONCE > /nonce.txt
snpguest report /report1.bin /nonce.txt
if [ $? -ne 0 ]; then
echo "!!! [INITRAMFS] FAILED to get attestation report."
exit 1
fi
echo " -> Generating ephemeral key pair..."
age-keygen -o /vm_key.txt
EPHEMERAL_PUBLIC_KEY=$(grep "public key: " /vm_key.txt | cut -d ' ' -f 4)
echo " -> Public key generated."
echo " -> Generating attestation report with public key..."
echo -n "$EPHEMERAL_PUBLIC_KEY " > /pub.txt # age pub key 长 62 bytes, 这里凑一个 64 bytes, 能直接传 key 原文就不用 hash 了
snpguest report /report2.bin /pub.txt
if [ $? -ne 0 ]; then
echo "!!! [INITRAMFS] FAILED to get attestation report."
exit 1
fi
echo " -> Attestation report generated."
echo "----- REPORT 1 -----"
cat /report1.bin | base64
echo "----- REPORT 2 -----"
cat /report2.bin | base64
read -r -p "Enter encrypted LUKS key (age format, one-line base64):" ENCRYPTED_LUKS_KEY_B64
echo $ENCRYPTED_LUKS_KEY_B64 | base64 -d > /luks.age
echo "==> [INITRAMFS] Decrypting LUKS key..."
DECRYPTED_LUKS_KEY=$(age --decrypt -i /vm_key.txt /luks.age)
if [ $? -ne 0 ] || [ -z "${DECRYPTED_LUKS_KEY}" ]; then
echo "!!! [INITRAMFS] FAILED to decrypt LUKS key."
exit 1
fi
rm /vm_key.txt /luks.age /pub.txt /report1.bin /report2.bin
echo "==> [INITRAMFS] Attempting to unlock LUKS device: ${LUKS_DEVICE}"
echo -n "${DECRYPTED_LUKS_KEY}" | cryptsetup luksOpen ${LUKS_DEVICE} ${MAPPER_NAME} --key-file -
if [ $? -ne 0 ]; then
echo "!!! [INITRAMFS] FAILED to unlock LUKS device with provided key."
exit 1
fi
echo "==> [INITRAMFS] LUKS device unlocked successfully as /dev/mapper/${MAPPER_NAME}"
echo "==> [INITRAMFS] Creating mount point for the real root..."
mkdir ${NEW_ROOT_MP}
echo "==> [INITRAMFS] Mounting decrypted root filesystem..."
mount -t ${FS_TYPE} /dev/mapper/${MAPPER_NAME} ${NEW_ROOT_MP}
if [ $? -ne 0 ]; then
echo "!!! [INITRAMFS] FAILED to mount decrypted root filesystem."
# exec /bin/sh # 仅供调试时使用
exit 1
fi
echo "==> [INITRAMFS] Real root filesystem mounted successfully."
echo "==> [INITRAMFS] Unmounting temp filesystems..."
umount /proc
umount /sys
umount /dev
echo "==> [INITRAMFS] Switching to real root and executing init..."
exec switch_root ${NEW_ROOT_MP} /sbin/init
echo "!!! [INITRAMFS] FAILED to switch_root."
exit 1
将如上的 init 和所有用到的工具打包到 initramfs 中. 在本地计算好 Measurement 值, 并将其发送到服务器, 准备工作就终于完成了!
我们启动虚拟机, 本地提供一个随机 nonce, 可以得到两份 report.bin 文件.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22[ 7.791052] Run /init as init process
==> [INITRAMFS] Starting LUKS init script...
==> [INITRAMFS] Mounting essential filesystems...
==> [INITRAMFS] Loading crypto modules...
[ 7.829855] sev-guest sev-guest: Initialized SEV guest driver (using vmpck_id 0)
[ 7.850841] xor: automatically using best checksumming function avx
[ 7.857134] async_tx: api initialized (async)
==> [INITRAMFS] Starting remote attestation process...
Enter nonce:f5c837b576373887112d46fcd20a77ac14ca5be663ca6d4913bd79e13e8d7c2e
-> Generating attestation report with nonce...
-> Generating ephemeral key pair...
Public key: age15lz45488nzmk3q4kmfdg9489s7652h0h04vdekjhnptwa5wnff3snv45ly
-> Public key generated.
-> Generating attestation report with public key...
-> Attestation report generated.
----- REPORT 1 -----
AgAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAJ
<下略...>
----- REPORT 2 -----
AgAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAJ
<下略...>
Enter encrypted LUKS key (age format, one-line base64):
我们在本地验证两个 Report 的签名, 再确认两份的 Measurement 值与我们本地测算的一致.
随后确认第一份的 Report Data 与我们随机生成的 nonce 一致, 取第二份的 Report Data 解码成为 age 公钥, 加密我们的 LUKS 密钥传递给虚拟机.1
2
3
4
5
6
7
8
9snpguest verify attestation -p genoa . report1.bin
# VEK signed the Attestation Report!
snpguest verify attestation -p genoa . report2.bin
# VEK signed the Attestation Report!
snpguest display report report1.bin # 验证 Measurement 和 Nonce
snpguest display report report2.bin # 验证 Measurement, 获得 age 公钥
echo -n "AMD,YES\!" | age -r age15lz45488nzmk3q4kmfdg9489s7652h0h04vdekjhnptwa5wnff3snv45ly | base64 -w 0
将得到的加密过后的密钥粘贴给虚拟机, 看到密钥正确加载, 启动流程继续, 我们成功进入了熟悉的 Ubuntu 22.04 系统~1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Enter encrypted LUKS key (age format, one-line base64):YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBITW5Bclp1ZmNiU01yZmlicmhpWmJaMG1KUExRTW5DNnI4Nmo3K3FZNTBBCkZwQU5EelNtNjN2SDlQeUtBVEVGZSs0dk5WWldMTUhLSTlCblY3c3k2bE0KLS0tIFhWR3IvY0NUSkZuemplS0FRa0ozbGRvQ0Y4WS81NDQrdGJQUkVyMXBiRWsKR3IVz1uPSJtD/Uc9ojYZ2fChdRumQk7YxAKv5JUbffWvFevi/Lhyww==
==> [INITRAMFS] Decrypting LUKS key...
==> [INITRAMFS] Attempting to unlock LUKS device: /dev/sda
==> [INITRAMFS] LUKS device unlocked successfully as /dev/mapper/encroot
==> [INITRAMFS] Creating mount point for the real root...
==> [INITRAMFS] Mounting decrypted root filesystem...
[493.983329] EXT4-fs (dm-1): mounted filesystem b155f911-e042-4949-b6bb-3a01f66445bf r/w with ordered data mode. Quota mode: none.
==> [INITRAMFS] Real root filesystem mounted successfully.
==> [INITRAMFS] Unmounting temp filesystems...
==> [INITRAMFS] Switching to real root and executing init...
[494.535955] systemd[1]: Failed to look up module alias 'autofs4': Function not implemented
[494.571956] systemd[1]: systemd 249.11-0ubuntu3 running in system mode (+PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT +GNUTLS -OPENSSL +ACL +BLKID +CURL +ELFUTILS -FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP -LIBFDISK +PCRE2 -PWQUALITY -P11KIT -QRENCODE +BZIP2 +LZ4 +XZ +ZLIB +ZSTD -XKBCOMMON +UTMP +SYSVINIT default-hierarchy=unified)
[494.578432] systemd[1]: Detected virtualization kvm.
[494.579548] systemd[1]: Detected architecture x86-64.
Welcome to Ubuntu 22.04 LTS!
<随后就进入正常的启动流程了>
很快启动成功, 我们就能看到 tty 上的登录 prompt. 但我们不要直接在 QEMU 终端中登录, 这是不安全的. 我们已经配置好 SSH, 并且预先记下了 Host Key 的公钥, 直接通过 SSH 即可安全地连接到加密的 VM.
启动成功后看起来平平无奇的 VM
尾声
第一次尝试自己使用可信计算技术, 除了要手动构建 QEMU 和内核外, 整体的流程和工具还不算复杂, 相比纯 SEV/SEV-ES 的 attestation 过程也简化了很多. 顺便回顾了 Linux 启动的流程, 构建了从 UEFI 固件 -> Kernel -> initramfs -> Ubuntu 的信任链. 动手之前也没想到, 一个最小的 initramfs 能够这么简单.
这篇博客也是来自我的突发奇想, 最终能在硬件和软件都完全不受信任的机器上构建出安全的计算环境, 这还是挺有趣的. 搞了好几天, 谁还记得开始我其实只是想校验一下我在服务器上的 restic 备份… 其实我这点文件直接明文放着都没人感兴趣.
本文采用 CC BY-NC-SA 4.0 许可协议发布.
作者: lyc8503, 文章链接: https://blog.lyc8503.net/post/amd-sev-snp/
如果本文给你带来了帮助或让你觉得有趣, 可以考虑赞助我¬_¬